import { Controller, Get, Post, Body, Patch, Param, Req, Inject, BadRequestException } from '@nestjs/common';
import { UserService } from './user.service';
import { SkipLogin } from 'src/common/decorator/public.decorator';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { ConfigService } from '@nestjs/config';
import { AuthService } from 'src/auth/auth.service';
import { encrypt, getRandomStr, getRedisKey, safeOutput, getAvatarUrl, getRandomName, validatePhone, validateEmail } from 'src/utils';
import { validateParams } from 'src/common/utils';
import { CreateUserDto } from './dto/create-user.dto';
import { plainToInstance } from 'class-transformer';
import { IsNull } from 'typeorm';
import { UpdateUserDto } from './dto/update-user.dto';
import { SmsCaptchaService } from 'src/sms-captcha/sms-captcha.service';
import { FileService } from 'src/file/file.service';
import { BASE_TYPE_LOGIN, BASE_TYPE_REG, IDENTITY, IDENTITY_MAP } from 'src/utils/config';
import { NotifyService } from 'src/notify/notify.service';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
import { ulid } from 'src/utils';

@Controller('user')
export class UserController {
    constructor(
        @Inject(CACHE_MANAGER) private cacheManager: Cache,
        @InjectQueue('user') private userQueue: Queue,
        private userService: UserService,
        private configService: ConfigService,
        private authService: AuthService,
        private smsService: SmsCaptchaService,
        private fileService: FileService,
        private notifyService: NotifyService,
    ) { }

    /**
     * 手机验证码登录
     *
     * @param {*} body { phone: string, smsCode: string, inviter_code?: string }
     * @return {*} 
     * @memberof UserController
     */
    @SkipLogin()
    @Post('/phone-sms-login')
    async phoneSmsLogin(@Body() body: any): Promise<any> {
        const { phone, smsCode, inviter_code = '' } = body;
        const sendAccount = phone;
        const autoRegister = !!this.configService.get('AUTO_REGISTER');
        const type = BASE_TYPE_LOGIN;
        const smsKey = getRedisKey(sendAccount, type, 'sms'); // 手机登录验证码 key
        const smsErrorCountKey = getRedisKey(sendAccount, type, 'err_count'); // 手机登录验证码连续错误次数 key
        const encryptAccount = encrypt(sendAccount);

        // 1. 验证 验证码
        const pass = await this.smsService.checkSmsCode(smsCode, sendAccount, type);
        if (!pass) throw new BadRequestException(`验证码错误`);

        // 2. 获取用户信息
        let userInfo: any = await this.userService.findUser({
            phone: encryptAccount,
        });

        // 3. 查询库里是否存在用户，判断是直接返回失败还是自动注册，这里需要额外处理下邀请码逻辑
        if (!userInfo || !userInfo.id) {
            if (autoRegister) {
                const inviterObj = inviter_code ? { inviter_code } : {};
                // 生成默认用户头像
                const avatarUrl = getAvatarUrl();
                const avatarObj = avatarUrl ? { avatar: avatarUrl } : {};

                const dbData = {
                    id: ulid(),
                    nickname: getRandomName(),
                    status: 1,
                    phone: encryptAccount,
                    invite_code: getRandomStr(8),
                    ...inviterObj,
                    ...avatarObj,
                };

                await validateParams(plainToInstance(CreateUserDto, dbData));
                userInfo = await this.userService.create(dbData);
                // 头像处理
                await this.userQueue.add('trans-default-avatar', { user_id: userInfo.id, avatar: avatarUrl }, { attempts: 2 }); // 重试2次
            } else {
                throw new BadRequestException('请注册后再使用');
            }
        }

        // 4. 判断用户状态
        if (userInfo?.deleted_at) throw new BadRequestException(`注销账号3个月内禁止注册，可联系客服确认`);
        if (userInfo && !userInfo.status) throw new BadRequestException('账号已被禁用，请联系客服');

        // 5. 生成accessToken, refreshToken
        const arToken = await this.authService.getToken(userInfo.id);

        // 6. 用户信息存入 redis
        const userRedisKey = getRedisKey(userInfo.id, 'user', 'info');

        // 7. 用户信息处理
        userInfo = safeOutput(userInfo);
        await this.cacheManager.store.set(userRedisKey, userInfo, this.configService.get('REDIS_USER_EXPIRE'));

        // 8. 清除Redis对应数据
        this.cacheManager.store.del(smsKey);
        this.cacheManager.store.del(smsErrorCountKey);

        // 9. 返回 用户信息、token 信息
        return { userInfo, token: arToken };
    }

    /**
     * 注册
     *
     * @param {*} body  入参 { phone, smsCode, username, avatar, password }
     */
    @SkipLogin()
    @Post('/register')
    async register(@Body() body: any) {
        const { phone, contact, ...rest } = body;
        const type = BASE_TYPE_REG;
        const smsKey = getRedisKey(phone, type, 'sms'); // 注册验证码 key
        const smsErrorCountKey = getRedisKey(phone, type, 'err_count'); // 注册验证码连续错误次数 key

        // 1. 验证 验证码
        const pass = await this.smsService.checkSmsCode(rest.smsCode, phone, type);
        if (!pass) throw new BadRequestException(`验证码错误`);

        // 2. 判断用户是否存在，存在不允许重复注册
        const userExist: any = await this.userService.findUser({ phone: encrypt(phone) });
        if (userExist) throw new BadRequestException(`请勿频繁注册`);
        // 4. 准备入库
        const userData = {
            phone: encrypt(phone),
            contact: encrypt(contact),
            ...rest,
        };
        await validateParams(plainToInstance(CreateUserDto, userData));

        // 5. 插入新用户
        let userInfo = await this.userService.create(userData);
        // 6. 生成token
        const arToken = await this.authService.getToken(userInfo.id);
        // 7. 用户信息存入 redis
        const userRedisKey = getRedisKey(userInfo.id, 'user', 'info');
        userInfo = safeOutput(userInfo);
        await this.cacheManager.store.set(
            userRedisKey,
            userInfo,
            this.configService.get('REDIS_USER_EXPIRE'),
        );
        // 8. 清除Redis里的短信验证码
        await this.cacheManager.store.del(smsKey);
        await this.cacheManager.store.del(smsErrorCountKey);
        // --8.2. 移动头像，如果用户头像存在，那么就启用队列，复制、移动头像；移动成功后需要更新redis
        await this.userQueue.add('move-avatar', { user_id: userInfo.id, avatar: userInfo.avatar }, { attempts: 2 });
        // 10. 返回 用户信息、token 信息
        return { ...userInfo, token: arToken };
    }

    /**
     * 注销
     *
     * @param {*} req
     * @return {*}
     * @memberof UserController
     */
    @Get('/logout')
    async logout(@Req() req: any): Promise<any> {
        const { userId } = req?.user || {};
        if (!userId) throw new BadRequestException('退出失败');

        const userRedisKey = getRedisKey(userId, 'user', 'info');
        await this.cacheManager.store.del(userRedisKey);
        await this.cacheManager.store.del(`${userId}_token`);
        await this.cacheManager.store.del(`${userId}_refresh`);

        return '退出成功';
    }

    /**
     * 注销账号
     *
     * @memberof UserController
     */
    @Post('/logoff')
    async logoff(@Req() req: any) {
        const { userId } = req.user;
        const userInfo = await this.userService.findUser({ id: userId, deleted_at: IsNull() });
        if (!userInfo) throw new BadRequestException(`无效请求`);

        const success = await this.userService.deleteUser(userId);
        if (!success) throw new BadRequestException(`账号注销失败，请稍后重试`);

        return `账号注销成功`;
    }


    /**
     * 更新用户名
     *
     * @param {*} body 入参 { nickname }
     * @param {*} req
     * @return {*} 
     * @memberof UserController
     */
    @Patch('/name')
    async updateName(@Body() body: any, @Req() req: any): Promise<any> {
        const { userId } = req.user || {};
        const postData = { nickname: body.nickname };
        if (!userId) throw new BadRequestException('无效请求');
        await validateParams(plainToInstance(UpdateUserDto, postData));

        return await this.userService.updateInfoWithCache(userId, postData); // 更新name, phone, password后需要更新redis
    }

    /**
     * 更新用户头像
     *
     * @param {*} body 入参 { avatar }
     * @param {*} req
     * @return {*} 
     * @memberof UserController
     */
    @Patch('/avatar')
    async updateAvatar(@Body() body: any, @Req() req: any): Promise<any> {
        const { userId } = req.user || {};
        if (!userId || !body.avatar) throw new BadRequestException('无效请求');
        const url = await this.fileService.moveToDir(body.avatar, 'avatar');
        if (!url) throw new Error(`头像更新失败，无图片地址`);

        return await this.userService.updateInfoWithCache(userId, { avatar: url });
    }

    /**
     * 更新用户密码/手机号/邮箱
     *
     * @param {*} body 入参 { password, confirm, identity, slug, smsCode, step, phone, email }
     * @param {*} req 
     * @param {string} field 要更新的字段，可选值：phone, email, password
     * @return {*}  {Promise<any>}
     * @memberof UserController
     */
    @Post('/:field')
    async updateField(@Body() body: any, @Req() req: any, @Param('field') field: string): Promise<any> {
        const { userId } = req.user || {};
        // step=1: identity, smsCode
        // step=2: password(confirm)/phone/email, smsCode, step, slug
        const { password, confirm, identity, slug, smsCode, step, phone, email } = body;

        if ([1, 2].includes(+step)) throw new BadRequestException('无效请求');
        if (!field || !['phone', 'email', 'password'].includes(field) || !body[field]) throw new BadRequestException('无效请求');
        if (identity) {
            if (!IDENTITY.includes(identity)) throw new BadRequestException('无效请求');
        } else {
            if (!phone && !email) throw new BadRequestException('无效请求');
            if (!smsCode) throw new BadRequestException('无效请求');
            if (phone && !validatePhone(phone)) throw new BadRequestException('无效手机号');
            if (email && !validateEmail(email)) throw new BadRequestException('无效邮箱');
        }

        // 获取账号
        const sendAccount = await this.smsService.getSmsAccount(userId, body);
        const smsKey = getRedisKey(sendAccount, slug, 'sms'); // 验证码 key
        // 校验验证码是否正确
        const smsOk = await this.smsService.checkSmsCode(smsCode, sendAccount, slug);
        if (1 === step) {
            if (!smsOk) throw new BadRequestException(`身份认证失败：无效验证码`);

            await this.smsService.markVerifyIdentity(userId, slug);
            await this.cacheManager.store.del(smsKey); // 清除redis验证码
            return `身份认证成功`;
        } else {
            if (!smsOk) throw new BadRequestException(`操作失败：无效验证码`);
        }

        const postData = { [field]: encrypt(body[field]) };
        // 更新MySQL
        const userInfo = await this.userService.updateInfoWithCache(userId, postData);

        // 清理Redis对应的key
        await this.cacheManager.store.del(smsKey);
        const verifiedKey = getRedisKey(userId, IDENTITY_MAP[slug], 'verified');
        await this.cacheManager.store.del(verifiedKey);

        return userInfo;
    }
}
